package org.scmpatrol.scm.connector.svn;

import org.apache.commons.lang.NotImplementedException;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.scmpatrol.scm.SCMCommit;
import org.scmpatrol.scm.SCMPath;
import org.scmpatrol.scm.action.*;
import org.scmpatrol.scm.common.exception.SCMPatrolException;
import org.scmpatrol.scm.common.exception.scm.SCMConnectException;
import org.scmpatrol.scm.common.exception.scm.SCMUnknownActionException;
import org.scmpatrol.scm.connector.SCMConnector;
import org.scmpatrol.scm.connector.SCMProperties;
import org.tmatesoft.svn.core.*;
import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory;
import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.wc.SVNClientManager;

import java.util.*;

public class SVNConnector implements SCMConnector {
    private final static Logger LOGGER = Logger.getLogger(SVNConnector.class.getName());

    private SVNURL repositoryURL = null;
    private SVNRepository repository;
    private SVNClientManager manager;

    private SVNRepository getRepository() throws SCMPatrolException {
        if (repository == null) {
            throw new SCMPatrolException(
                    "SVN repository not intialised. Call init method first");
        }
        return repository;
    }

    public void init(SCMProperties properties) throws SCMPatrolException {
        LOGGER.debug("Create connection for " + properties);

        try {
            repositoryURL = SVNURL.parseURIDecoded(properties.getUrl());
        } catch (SVNException e) {
            StringBuffer message = new StringBuffer("Error creation SVNURL : ")
                    .append(e.getMessage());
            LOGGER.error(message, e);
            throw new SCMPatrolException(message.toString());
        }

        // TODO put svnkit initialization on plugin initialization
        // with this here, we call factories initialization each time we are
        // creating a repository
        try {
            String protocol = repositoryURL.getProtocol();
            if (StringUtils.equalsIgnoreCase("file", protocol)) {
                LOGGER.info("Initializing local repository factory");
                FSRepositoryFactory.setup();
            } else if (StringUtils.equalsIgnoreCase("http", protocol)) {
                LOGGER.info("Initializing http repository factory");
                DAVRepositoryFactory.setup();
            } else if (StringUtils.equalsIgnoreCase("svn", protocol)) {
                LOGGER.info("Initializing svn repository factory");
                SVNRepositoryFactoryImpl.setup();
            } else {
                throw new SCMPatrolException("Unknown protocol " + protocol);
            }

            manager = SVNClientManager.newInstance();
            repository = manager.createRepository(repositoryURL, false);

        } catch (SVNException e) {
            StringBuffer message = new StringBuffer(
                    "Erreur connecting to svn server : ")
                    .append(e.getMessage());
            LOGGER.info(message, e);
            throw new SCMConnectException(message.toString());
        }
    }

    @SuppressWarnings("unchecked")
    public List<SCMCommit> getCommits(Date from) throws SCMPatrolException {
        List<SCMCommit> result = new ArrayList<SCMCommit>();

        try {
            Long fromRevisionId = repository.getDatedRevision(from);

            Collection<SVNLogEntry> revisions = (Collection<SVNLogEntry>) repository
                    .log(new String[]{""}, null,
                            Math.max(1, fromRevisionId), repository
                                    .getLatestRevision(), true, true);

            // getDatedRevision returns the closest revision, we have to
            // verify the commit date of the first returned revision is gt the
            // passed date
            if (!revisions.isEmpty()) {
                SVNLogEntry entry = revisions.iterator().next();

                if (!entry.getDate().after(from)) {
                    LOGGER
                            .debug("First revision is before asked date. Removing it...");
                    revisions.remove(entry);
                }
            }

            for (SVNLogEntry entry : revisions) {
                Long revision = entry.getRevision();
                LOGGER.info("Getting details for revision " + revision);

                SCMCommit commit = getCommit(revision.toString());

                result.add(commit);
            }
        } catch (SVNException e) {
            StringBuffer message = new StringBuffer(
                    "Error getting revision information : ").append(e
                    .getMessage());
            LOGGER.info(message, e);
            throw new SCMConnectException(message.toString());
        }
        return result;
    }

    public List<SCMCommit> getCommits(String from, int limit) throws SCMPatrolException {
        List<SCMCommit> result = new ArrayList<SCMCommit>();

        try {
            Long fromRevisionId = new Long(from == null ? "1" : from);

            // TODO use here the log method taking a limit parameter
            Collection<SVNLogEntry> revisions = (Collection<SVNLogEntry>) repository
                    .log(new String[]{""}, null,
                            Math.max(1, fromRevisionId),
//							repository.getLatestRevision(),
                            fromRevisionId + limit,
                            true, true);

            for (SVNLogEntry entry : revisions) {
                Long revision = entry.getRevision();
                LOGGER.info("Getting details for revision " + revision);

                SCMCommit commit = getCommit(String.valueOf(revision));

                result.add(commit);
            }
        } catch (SVNException e) {
            StringBuffer message = new StringBuffer(
                    "Error getting revision information : ").append(e
                    .getMessage());
            LOGGER.info(message, e);
            throw new SCMConnectException(message.toString());
        }
        return result;
    }

    public boolean haveNewCommits(Date from) {
        throw new NotImplementedException();
    }

    public boolean haveNewCommits(String from) {
        throw new NotImplementedException();
    }

    public String getLastRevisionId() throws SCMPatrolException {
        SVNRepository repository = getRepository();

        long revision;

        try {
            revision = repository.getLatestRevision();
        } catch (SVNException e) {
            StringBuffer message = new StringBuffer(
                    "Unable to get last revision ").append(e.getMessage());
            LOGGER.error(message, e);
            throw new SCMPatrolException(message.toString());
        }
        return Long.toString(revision);
    }

    @SuppressWarnings("unchecked")
    public SCMCommit getCommit(String revisionId) throws SCMPatrolException {
        Long revision = Long.valueOf(revisionId);

        try {
            // TODO put this in a separate method
            Collection<SVNLogEntry> revisions = (Collection<SVNLogEntry>) repository
                    .log(new String[]{""}, null, revision, revision, true,
                            true);

            repository.closeSession();

            if (revisions.size() != 1) {
                throw new SCMPatrolException(
                        "Error getting modification for revision : "
                                + revisionId);
            }

            // Getting commit log (date, user, list of modified url)

            SCMCommit result = convertLog(revisions).get(0);

            // put this in a private method and factorize with get commits			
            // Getting the commit details (diff, ...)
            SVNReporterBaton reporter = new SVNReporterBaton(revisionId);

            reporter.setActions(result.getActions());

            SVNEditor editor = new SVNEditor(manager, repositoryURL,
                    revision, result);

            repository.diff(repository.getLocation(), revision, revision, null,
                    false, SVNDepth.INFINITY, true, reporter, editor);

            return result;
        } catch (SVNException e) {
            StringBuffer message = new StringBuffer(
                    "Error getting revision information : ").append(e
                    .getMessage());
            LOGGER.info(message, e);
            throw new SCMConnectException(message.toString());

        }

    }

    private List<SCMCommit> convertLog(Collection<SVNLogEntry> revisions)
            throws SCMPatrolException {
        List<SCMCommit> result = new ArrayList<SCMCommit>(revisions.size());

        for (SVNLogEntry revision : revisions) {

            SCMCommit commit = new SCMCommit();
            commit.setScmRevision(String.valueOf(revision.getRevision()));
            commit.setAuthor(revision.getAuthor());
            commit.setMessage(revision.getMessage());
            commit.setDate(revision.getDate());

            Map<String, SVNLogEntryPath> changes = (Map<String, SVNLogEntryPath>) revision
                    .getChangedPaths();

            for (SVNLogEntryPath entry : changes.values()) {
                SCMAction action = null;

                switch (entry.getType()) {
                    case SVNLogEntryPath.TYPE_ADDED:
                        SCMAddAction addAction = new SCMAddAction(new SCMPath(entry.getPath()));
                        action = addAction;

                        addAction.setCopiedFrom(entry.getCopyPath());
                        addAction.setCopiedFromRevision(String.valueOf(entry
                                .getCopyRevision()));

                        break;
                    case SVNLogEntryPath.TYPE_DELETED:
                        SCMDeleteAction delAction = new SCMDeleteAction(new SCMPath(entry
                                .getPath()));
                        action = delAction;

                        break;
                    case SVNLogEntryPath.TYPE_MODIFIED:
                        SCMModificationAction modAction = new SCMModificationAction(new SCMPath(
                                entry.getPath()));
                        action = modAction;

                        modAction.setCopiedFrom(entry.getCopyPath());
                        modAction.setCopiedFromRevision(String.valueOf(entry
                                .getCopyRevision()));

                        break;
                    case SVNLogEntryPath.TYPE_REPLACED:
                        SCMReplaceAction repAction = new SCMReplaceAction(new SCMPath(entry
                                .getPath()));
                        action = repAction;

                        repAction.setCopiedFrom(entry.getCopyPath());
                        repAction.setCopiedFromRevision(String.valueOf(entry
                                .getCopyRevision()));

                        break;
                    default:
                        StringBuffer message = new StringBuffer(
                                "Unknown SVN action : ").append(entry.getType())
                                .append(" for revision ").append(
                                        revision.getRevision());
                        LOGGER.error(message.toString());
                        throw new SCMUnknownActionException(message.toString());
                }

                LOGGER.debug("Action added to commit : " + action);

                commit.addAction(action);
            }
            result.add(commit);
        }
        return result;
    }

}
